/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.rpgmapper.manager;

import com.jme3.asset.AssetManager;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.TouchInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.TouchListener;
import com.jme3.input.controls.TouchTrigger;
import com.jme3.input.event.TouchEvent;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import com.jme3.scene.shape.Quad;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture.MagFilter;
import com.jme3.texture.Texture2D;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.rpgmapper.JmeMain;
import org.rpgmapper.character.CharacterManager;
import org.rpgmapper.tools.AppContext;
import org.rpgmapper.tools.ImageUtils;

/**
 *
 * @author Nehon
 */
public class MapManager implements CharacterSelectionListener {

    private ColorRGBA[] grid;
    private int gridSize = 0;
    private Texture2D mainTexture;
    private Texture2D backupTexture;
    private boolean dirty = true;
    private Geometry ground;
    private Geometry dummy;
    private Node mapNode = new Node("mapNode");
    private Node charactersNode = new Node("charactersNode");
    private Material groundMat;
    private Material dummyMat;
    private int updateIn = -1;
    private static final int UPDATE_DELAY = 2;
    private CharacterManager selectedCharacter = null;
    private Map<String, CharacterManager> characters = new HashMap<String, CharacterManager>();
    boolean moving = false;
    private DrawingManager drawingManager;
    private boolean snapToGrid = true;

    public void onCharacterSelect(String id) {
        setSelectedCharacter(getCharacter(id));
    }

    public enum Pattern {

        Move16(new int[]{1, 3, 5, 7, 8, 9, 10, 11, 12, 13, 13, 14, 14, 15, 15, 16}),
        Move15(new int[]{1, 3, 5, 7, 8, 9, 10, 11, 12, 12, 13, 13, 14, 14, 15}),
        Move14(new int[]{1, 3, 5, 7, 8, 9, 10, 11, 11, 12, 12, 13, 13, 14}),
        Move13(new int[]{1, 3, 5, 7, 8, 9, 10, 10, 11, 11, 12, 12, 13}),
        Move12(new int[]{1, 3, 5, 7, 8, 9, 9, 10, 10, 11, 11, 12}),
        Move11(new int[]{1, 3, 5, 7, 8, 8, 9, 9, 10, 10, 11}),
        Move10(new int[]{1, 3, 5, 7, 7, 8, 8, 9, 9, 10}),
        Move9(new int[]{1, 3, 5, 6, 7, 7, 8, 8, 9}),
        Move8(new int[]{1, 3, 5, 6, 6, 7, 7, 8}),
        Move7(new int[]{1, 3, 4, 5, 6, 6, 7}),
        Move6(new int[]{1, 3, 4, 5, 5, 6}),
        Move5(new int[]{1, 3, 4, 4, 5}),
        Move4(new int[]{1, 2, 3, 4}),
        Move3(new int[]{1, 2, 3}),
        Move2(new int[]{1, 2}),
        Controlled(new int[]{1});
        private int[] pattern;

        Pattern(int[] p) {
            pattern = p;
        }

        public int[] getPattern() {
            return pattern;
        }
    }

    public enum CellColor {

        None(ColorRGBA.White),
        Selected(ColorRGBA.Yellow),
        Aoo(ColorRGBA.Red),
        Move(new ColorRGBA(0.6f, 0.6f, 1.0f, 1.0f)),
        Character(ColorRGBA.Green);
        private ColorRGBA color;

        CellColor(ColorRGBA color) {
            this.color = color;
        }

        public ColorRGBA getColor() {
            return color;
        }
    }

    public MapManager(int gridSize, AssetManager assetManager) {
        Quad quad = new Quad(gridSize, gridSize);

        ground = new Geometry("ground", quad);
        ground.setLocalTranslation(new Vector3f(-gridSize / 2, 0, gridSize / 2));
        ground.setLocalRotation(new Quaternion().fromAngles(-FastMath.HALF_PI, 0, 0));
        ground.addControl(new TextureControl());

        groundMat = assetManager.loadMaterial("Textures/BrickWall/groundMat.j3m");
        //  groundMat.setTexture("LightMap", assetManager.loadTexture("Textures/MapCity/LightGround.jpg"));
        // groundMat.setTexture("LightMap", assetManager.loadTexture("Textures/drawing.png"));

        groundMat.setInt("GridSize", gridSize);

        Quad q2 = new Quad(0.1f, 0.1f);
        dummy = new Geometry("dummy", q2);
        dummy.setCullHint(Spatial.CullHint.Never);
        dummy.setLocalTranslation(new Vector3f(-gridSize / 2, -5, gridSize / 2));
        dummyMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        dummy.setMaterial(dummyMat);


        groundMat.setInt("GridSize", gridSize);


        grid = new ColorRGBA[gridSize * gridSize];

        this.gridSize = gridSize;
        mainTexture = new Texture2D(gridSize, gridSize, 1, Format.RGBA8);
        mainTexture.setMagFilter(MagFilter.Nearest);
        backupTexture = new Texture2D(gridSize, gridSize, 1, Format.RGBA8);
        backupTexture.setMagFilter(MagFilter.Nearest);
        ByteBuffer buf = BufferUtils.createByteBuffer(gridSize * gridSize * 4);
        mainTexture.getImage().getData().add(buf);
        ByteBuffer buf2 = BufferUtils.createByteBuffer(gridSize * gridSize * 4);
        backupTexture.getImage().getData().add(buf2);

        groundMat.setTexture("GridMap", mainTexture);
        ground.setMaterial(groundMat);
        initGrid();
        clear();



        ImageUtils.convert(grid, Format.RGBA8, mainTexture.getImage().getData(0));
        mainTexture.getImage().setUpdateNeeded();
        groundMat.setTexture("GridMap", mainTexture);
        ByteBuffer b1 = backupTexture.getImage().getData(0);
        b1.rewind();
        ByteBuffer b2 = mainTexture.getImage().getData(0);
        b2.rewind();
        b1.put(b2);
        backupTexture.getImage().setUpdateNeeded();
        dummyMat.setTexture("ColorMap", backupTexture);


        mapNode.attachChild(dummy);
        mapNode.attachChild(ground);

        drawingManager = new DrawingManager(groundMat, dummyMat);


    }

    public void registerInput(InputManager inputManager) {
        inputManager.addListener(new ActionListener() {

            public void onAction(String name, boolean isPressed, float tpf) {
                if (selectedCharacter != null) {
                    if (name.equals("startMove") && isPressed) {
                       AppContext.getSelectionManager().setMode(SelectionManager.Mode.Cell);
                        selectedCharacter.startMove();

                    }
                    if (name.equals("doneMove") && isPressed) {
                        AppContext.getSelectionManager().setMode(SelectionManager.Mode.Character);
                        selectedCharacter.doMove();

                    }
                    if (name.equals("draw") && isPressed) {
                        AppContext.getSelectionManager().setMode(SelectionManager.Mode.Draw);
                    }


                }
            }
        }, "startMove", "doneMove");
        inputManager.addMapping("startMove", new KeyTrigger(KeyInput.KEY_SPACE));
        inputManager.addMapping("draw", new KeyTrigger(KeyInput.KEY_D));
        inputManager.addMapping("doneMove", new KeyTrigger(KeyInput.KEY_RETURN));

        inputManager.addListener(new TouchListener() {

            public void onTouch(String name, TouchEvent event, float tpf) {
                if (event.getType() == TouchEvent.Type.LONGPRESSED) {
                    if (selectedCharacter != null) {
                        if (!moving) {
                            setMoveMode();
                        } else {
                            moving = false;
                            AppContext.getSelectionManager().setMode(SelectionManager.Mode.Character);
                            selectedCharacter.doMove();
                        }
                    }
                }
            }
        }, "all");

        inputManager.addMapping("all", new TouchTrigger(TouchInput.ALL));
    }

    public void setMoveMode() {
        if (selectedCharacter != null) {
            moving = true;
            AppContext.getSelectionManager().setMode(SelectionManager.Mode.Cell);
            selectedCharacter.startMove();
        }
    }

    private void initGrid() {
        for (int i = 0; i < gridSize * gridSize; i++) {
            grid[i] = new ColorRGBA();
        }
    }

    public void setColor(int index, CellColor color) {
        if (index != -1) {
            grid[index].set(color.getColor());
            dirty = true;
        }
    }

    public void mixColor(int index, CellColor color) {
        if (index != -1) {
            if (!grid[index].equals(CellColor.None.getColor())) {
                grid[index].interpolate(grid[index], color.getColor(), 0.5f);
            } else {
                grid[index].set(color.getColor());
            }
            dirty = true;
        }
    }

    public void setColorPattern(int index, CellColor color, Pattern pattern, boolean mix) {
        if (index != -1) {
            int[] p = pattern.getPattern();
            int size = p[p.length - 1];
            int start = index - size * gridSize;

            int curIndex = start;
            for (int i = 0; i < p.length; i++) {

                for (int j = curIndex - p[i]; j <= curIndex + p[i]; j++) {
                    if (j >= 0 && j < grid.length) {
                        if (mix) {
                            mixColor(j, color);
                        } else {
                            setColor(j, color);
                        }
                    }
                }
                curIndex += gridSize;
            }

            for (int j = curIndex - size; j <= curIndex + size; j++) {
                if (j >= 0 && j < grid.length) {
                    if (mix) {
                        mixColor(j, color);
                    } else {
                        setColor(j, color);
                    }
                }
            }
            curIndex += gridSize;

            for (int i = p.length - 1; i >= 0; i--) {

                for (int j = curIndex - p[i]; j <= curIndex + p[i]; j++) {
                    if (j >= 0 && j < grid.length) {
                        if (mix) {
                            mixColor(j, color);
                        } else {
                            setColor(j, color);
                        }
                    }
                }
                curIndex += gridSize;
            }

            dirty = true;
        }

    }

    public void updateTexture() {
        if (dirty) {

            groundMat.setTexture("GridMap", backupTexture);
            ImageUtils.convert(grid, Format.RGBA8, mainTexture.getImage().getData(0));
            mainTexture.getImage().setUpdateNeeded();
            dummyMat.setTexture("ColorMap", mainTexture);
            updateIn = UPDATE_DELAY;
            dirty = false;
        }
    }

    public Texture2D getTexture() {

        updateTexture();

        return mainTexture;
    }

    public final void clear() {
        for (int i = 0; i < grid.length; i++) {
            grid[i].set(CellColor.None.getColor());
        }
    }

    public int getGridSize() {
        return gridSize;
    }

    public void setGridSize(int gridSize) {
        this.gridSize = gridSize;
    }

    public Geometry getGroundGeometry() {
        return ground;
    }

    public Node getGroundNode() {
        return mapNode;
    }

    public int getIndex(Vector3f coordinates) {
        float x = coordinates.x + (float) gridSize * 0.5f;
        float z = -coordinates.z + (float) gridSize * 0.5f;
        return (int) (Math.floor(x) + gridSize * (Math.floor(z)));
    }

    public Vector2f getPixel(Vector3f coordinates, Vector2f store) {
        //      System.out.println( Math.round(coordinates.x) );
        if (snapToGrid) {
            float x = (Math.round(coordinates.x) + (float) gridSize / 2f) / (float) gridSize;
            float z = -(Math.round(coordinates.z) - (float) gridSize / 2f) / (float) gridSize;
            store.set(x, z);
            return store;

            //return new Vector2f(x,z);
        } else {
            float x = (coordinates.x + (float) gridSize / 2f) / (float) gridSize;
            float z = -(coordinates.z - (float) gridSize / 2f) / (float) gridSize;
            store.set(x, z);
            return store;
            //return new Vector2f(x,z);
        }

    }

    public Vector3f getCoordinates(int index, Vector3f store) {
        if (store == null) {
            store = new Vector3f();
        }
        float x = (float) Math.floor((float) index % (float) gridSize);
        float z = (float) Math.floor((float) index / (float) gridSize);
        store.x = x + 0.5f - (float) gridSize * 0.5f;
        store.z = -z - 0.5f + (float) gridSize * 0.5f;
        store.y = 0;
        return store;

    }

    public CharacterManager getSelectedCharacter() {
        return selectedCharacter;
    }

    public void setSelectedCharacter(CharacterManager selectedCharacter) {
        if (this.selectedCharacter != null) {
            setColor(this.selectedCharacter.getCurrentCell(), MapManager.CellColor.None);
        }
        this.selectedCharacter = selectedCharacter;
        if (selectedCharacter != null) {
            setColor(selectedCharacter.getCurrentCell(), MapManager.CellColor.Selected);
            AppContext.getRtsCam().lookAt(selectedCharacter.getModel().getLocalTranslation());
        }
        updateTexture();

    }

    private class TextureControl extends AbstractControl {

        @Override
        protected void controlUpdate(float tpf) {
            if (updateIn > -1) {
                if (updateIn == 0) {
                    groundMat.setTexture("GridMap", mainTexture);
                    ByteBuffer b1 = backupTexture.getImage().getData(0);
                    b1.rewind();
                    ByteBuffer b2 = mainTexture.getImage().getData(0);
                    b2.rewind();
                    b1.put(b2);
                    backupTexture.getImage().setUpdateNeeded();
                    dummyMat.setTexture("ColorMap", backupTexture);
                    updateIn--;
                } else {
                    updateIn--;
                }
            }
        }

        @Override
        protected void controlRender(RenderManager rm, ViewPort vp) {
        }

        public Control cloneForSpatial(Spatial spatial) {
            return null;
        }

        public TextureControl() {
        }
    }

    public void addCharacter(CharacterManager cm) {
        characters.put(cm.getModel().getName(), cm);
        charactersNode.attachChild(cm.getModel());
    }

    public void removeCharacter(CharacterManager cm) {
        if (selectedCharacter == cm) {
            setSelectedCharacter(null);
        }
        characters.remove(cm.getModel().getName());
        charactersNode.getChild(cm.getModel().getName()).removeFromParent();
    }

    public CharacterManager getCharacter(String id) {
        return characters.get(id);
    }

    public Iterator<CharacterManager> characterIterator() {
        return characters.values().iterator();
    }

    public Node getCharactersNode() {
        return charactersNode;
    }

    public DrawingManager getDrawingManager() {
        return drawingManager;
    }

    public boolean isSnapToGrid() {
        return snapToGrid;
    }

    public void setSnapToGrid(boolean snapToGrid) {
        this.snapToGrid = snapToGrid;
    }
}
